Here we are going to implement an algorithm to find out the \(LU\)-Decomposition of a Matrix Efficiently. We will then use it to solve System of Equations. ### OBJECTIVE Implement the efficient version of Crout’s decomposition. Solve a system \(AX=b\) by forward and backward substitution.
Whenever we have a Matrix whose \(LU\)-Decomposition in possible, where \(L\) is lower triangular Matrix and \(U\) is upper Triangular Matrix with \(1\) across it’s diagonal. We can write \(A\) as a factorization \(A=LU\) and we can solve the Matrix System \[Ax = b\] \[LUx = b\] Now, we can solve 2 Systems Quickly by Forward and Backward Substitution to get the Solution that is \[Ly = b\ (Forward\ Substitution)\] \[Ux = y\ (Backward\ Substitution)\] Thus, we get the solution \(x\) to the original system.
# Computes the L matrix
L <- function(A, j) {
n <- nrow(A)
tot <- rep(0, n-j+1)
if (j > 1){
for (k in 1:(j-1))
tot <- tot + A[j:n, k] * A[k, j]
}
A[(j:n), j] <- A[(j:n), j] - tot
return(A)
}
# Computes the U matrix
U <- function(A, i) {
n <- nrow(A)
tot <- rep(0, n-i)
if (i > 1){
for (k in 1:(i-1))
tot <- tot + A[k, (i+1):n] * A[i, k]
}
if(A[i, i] == 0)
stop("Singular Matrix Detected!")
A[i, (i+1):n] <- (A[i, (i+1):n] - tot) / A[i, i]
return(A)
}
# Computes the LU Decomposition
Compute.LU <- function(A) {
if(nrow(A) != ncol(A))
stop("Matrix is not square!")
n <- nrow(A)
for(i in 1:n) {
A <- L(A, i)
if(i < n)
A <- U(A, i)
}
L <- matrix(rep(0, n^2), n, n)
U <- diag(n)
for(j in 1:n)
L[j:n,j] <- A[j:n, j]
for(i in 1:(n-1))
U[i, (i+1):n] <- A[i, (i+1):n]
return(list("A" = A, "L"=L, "U"=U))
}
Let’s Try it out on some Matrix.
A <- matrix(c(1,2,3,5,6,7,9,11,12), 3, 3)
kable(A)
1 | 5 | 9 |
2 | 6 | 11 |
3 | 7 | 12 |
The \(LU\)-Decomposition of the above Matrix.
decomp <- Compute.LU(A)
kable(decomp$A, caption = "Packed Matrix")
1 | 5 | 9.00 |
2 | -4 | 1.75 |
3 | -8 | -1.00 |
kable(decomp$L, caption = "L")
1 | 0 | 0 |
2 | -4 | 0 |
3 | -8 | -1 |
kable(decomp$U, caption = "U")
1 | 5 | 9.00 |
0 | 1 | 1.75 |
0 | 0 | 1.00 |
The Specification of \(L\) an \(U\) being lower and upper triangular matrix with \(U\) having diagonal elements as \(1\) is met. Let’s test if \(A = LU\).
kable(decomp$L %*% decomp$U, caption = "LU")
1 | 5 | 9 |
2 | 6 | 11 |
3 | 7 | 12 |
Indeed we get back the original matrix. - Now, we are going to use the above \(LU\)-Decomposition to obtain the solution for the System \(Ax=b\). The Following code implements this Forward Substitution and Backward Substitution as discussed earlier.
# Solve Linear System
Solve.LU <- function(A, b){
LU <- Compute.LU(A)
n <- nrow(A)
# Forward Substitution
y <- rep(0, n)
if (LU$A[1,1] == 0)
stop("No Solution!")
y[1] <- b[1] / LU$A[1,1]
for (i in 2:n) {
if (LU$A[i,i] == 0)
stop("No Solution!")
y[i] <- (b[i] - sum(LU$A[i, 1:(i-1)]*y[1:(i-1)])) / LU$A[i,i]
}
# Backward Substitution
x <- rep(0, n)
x[n] <- y[n]
for (i in (n-1):1) {
x[i] <- y[i] - sum(LU$A[i, (i+1):n]*x[(i+1):n])
}
return(list("LU"=LU, "x"=x))
}
Let’s Test it on the Following System.
A <- matrix(c(2,4,-6,4,3,7,-10,6,0,2,0,4,0,0,1,5), 4, 4)
kable(A, caption = "A")
2 | 3 | 0 | 0 |
4 | 7 | 2 | 0 |
-6 | -10 | 0 | 1 |
4 | 6 | 4 | 5 |
kable(c(1,2,1,0), col.names = "b")
b |
---|
1 |
2 |
1 |
0 |
The solution is
sol <- Solve.LU(A, c(1,2,1,0))
kable(sol$x, caption = "Soln.")
x |
---|
11.500000 |
-7.333333 |
3.666667 |
-3.333333 |
Let’s check if \(Ax=b\).
kable(A %*% sol$x, col.names = "Ax")
Ax |
---|
1 |
2 |
1 |
0 |
Indeed we get \(b\) and hence it is a solution to the System.